/*
 * Copyright (C) 2009 M. Hautle.
 * 
 * This file is part of TrackedBean.
 * 
 * TrackedBean is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 * TrackedBean is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with TrackedBean. If not, see <http://www.gnu.org/licenses/>.
 */
package ch.trackedbean.binding.guicomponents;

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;
import javax.swing.*;
import ch.trackedbean.binding.beanSpec.*;
import ch.trackedbean.binding.guicomponents.action.*;
import ch.trackedbean.binding.guicomponents.renderers.*;
import ch.trackedbean.binding.mapping.*;
import ch.trackedbean.loaders.lists.*;

/**
 * Panel holding a table and it's mapping.<br>
 * If you need to add selection dependent {@link ISelectionAction}s then have a look at {@link TableSelectionActionHandler}.
 * 
 * @author M. Hautlem
 * @param <T> The bean type
 */
public class TablePanel<T> extends JPanel {
	/** The name of the model property. */
	public static final String MODEL_PROPERTY = "model";

	/** The table show on this panel */
	protected final JTable table;

	/** Flag indicating if the backgroundcolor should be adapted according to the status of the represented cell. */
	protected final boolean showStatus;

	/** Default action which will be executed when a double click occours. */
	protected ISelectionAction<T> defaultAction;

	/** The popup menu. */
	protected JPopupMenu popup = new JPopupMenu();

	/** The popup manager. */
	protected LocalPopupManager popupManager = new LocalPopupManager();

	/** The mapping of this table panel */
	protected TableMapping<T> mapping;

	/**
	 * Default constructor
	 * 
	 * @param readOnly True if the table binding is just read only
	 * @param showStatus True if the cellbackground should be adapted according to the cell state
	 */
	public TablePanel(boolean readOnly, boolean showStatus) {
		this(new JTable(), readOnly, showStatus);
	}

	/**
	 * Constructor with specified table
	 * 
	 * @param table The table to use
	 * @param readOnly True if the table binding is just read only
	 * @param showStatus True if the cellbackground should be adapted according to the cell state
	 */
	public TablePanel(final JTable table, boolean readOnly, boolean showStatus) {
		this.table = table;
		this.showStatus = showStatus;
		mapping = new TableMapping<T>(table, readOnly);
		table.setDefaultRenderer(Object.class, new DefaultPropertyCellRenderer(mapping, showStatus));
		// mouse listener fiering rowChoosen Events on double clicks
		table.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(final MouseEvent e) {
				if (defaultAction == null || e.getClickCount() < 2)
					return;
				if (TablePanel.this.table.getSelectedRowCount() != 1)
					return;
				final T row = getSelectedRow();
				// execute the default action
				if (defaultAction.selectionChanged(Collections.singletonList(row)))
					defaultAction.actionPerformed(new ActionEvent(TablePanel.this, 0, "Default Action"));
			}

			/**
			 * {@inheritDoc}
			 */
			@Override
			public void mousePressed(MouseEvent e) {
				showPopup(e);
			}

			/**
			 * {@inheritDoc}
			 */
			@Override
			public void mouseReleased(MouseEvent e) {
				showPopup(e);
			}

			/**
			 * Shows the popup menu if the popup trigger is set.<br>
			 * This method is called either from mousePressed or mouseReleased (depends on the host plattform...).
			 * 
			 * @param e The mouse event
			 */
			private void showPopup(MouseEvent e) {
				if (!e.isPopupTrigger() || TablePanel.this.table.getSelectedRowCount() == 0)
					return;
				popupManager.populate(popup, getSelectedRows());
				if (popup.getComponentCount() > 0)
					popup.show(e.getComponent(), e.getX(), e.getY());
			}
		});
		setLayout(new BorderLayout());
		add(new JScrollPane(table), BorderLayout.CENTER);
	}

	/**
	 * Returns the table of this panel.
	 * 
	 * @return The table.
	 */
	public JTable getTable() {
		return table;
	}

	/**
	 * Returns the {@link TableMapping} of this panel.
	 * 
	 * @return The mapping
	 */
	public TableMapping<T> getMapping() {
		return mapping;
	}

	/**
	 * Adds a column binding
	 * 
	 * @param label The column label
	 * @param path The path to the property shown in this column
	 * @param type The type of this column
	 * @return The {@link ColumnBindingInfos} object for further configuration of the binding
	 * @throws IllegalStateException if the table is already bound
	 * @see ch.trackedbean.binding.mapping.TableMapping#addColumn(java.lang.String, java.lang.String, java.lang.Class,boolean)
	 */
	public ColumnBindingInfos addColumn(final String label, final String path, final Class<?> type) {
		return mapping.addColumn(label, path, type, true);
	}

	/**
	 * Adds a column binding
	 * 
	 * @param label The column label
	 * @param path The path to the property shown in this column
	 * @return The {@link ColumnBindingInfos} object for further configuration of the binding
	 * @throws IllegalStateException if the table is already bound
	 * @see ch.trackedbean.binding.mapping.TableMapping#addColumn(java.lang.String, java.lang.String,boolean)
	 */
	public ColumnBindingInfos addColumn(final String label, final String path) {
		return mapping.addColumn(label, path, true);
	}

	/**
	 * Adds a column binding.<br>
	 * The passed el string is used to extract the display values out of the provided row objects.<br>
	 * This call sets simply an {@link SimpleELTableCellRenderer} on the representet column.
	 * 
	 * @param label The column label
	 * @param property The property shown in this column
	 * @param el An string containing el expression escaped in <code>${el}</code> blocks
	 * @return The {@link ColumnBindingInfos} object for further configuration of the binding
	 * @throws IllegalStateException if the table is already bound
	 * @see ch.trackedbean.binding.mapping.TableMapping#addColumn(java.lang.String, java.lang.String, boolean)
	 */
	public ColumnBindingInfos addColumn(final String label, final String property, String el) {
		final ColumnBindingInfos bind = mapping.addColumn(label, property, true);
		bind.setRenderer(new SimpleELTableCellRenderer(el, "", getMapping(), showStatus));
		bind.setEditable(false);
		return bind;
	}

	/**
	 * Adds a column binding with a {@link JComboBox} using a {@link SimpleELListRenderer} with the given el expression.<br>
	 * The combobox content list will be loaded using {@link ListManager#getList(Class, String, Map)}.
	 * 
	 * @param label The column label
	 * @param path The path to the property shown in this column
	 * @param el The simple el expression to use by the combobox renderer
	 * @param type The content type of the combobox list
	 * @return The {@link BindingInfos} object for further configuration of the binding
	 * @throws IllegalStateException if the table is already bound
	 */
	public ColumnBindingInfos addComboBoxColumn(final String label, final String path, String el, Class<?> type) {
		return addComboBoxColumn(label, path, el, type, null, null);
	}

	/**
	 * Adds a column binding with a {@link JComboBox} using a {@link SimpleELListRenderer} with the given el expression.<br>
	 * The combobox content list will be loaded using {@link ListManager#getList(Class, String, Map)}.
	 * 
	 * @param label The column label
	 * @param path The path to the property shown in this column
	 * @param el The simple el expression to use by the combobox renderer
	 * @param type The content type of the combobox list
	 * @param subType Additional string specifying the "subtype" of the combobox content list (may be null)
	 * @return The {@link BindingInfos} object for further configuration of the binding
	 * @throws IllegalStateException if the table is already bound
	 */
	public ColumnBindingInfos addComboBoxColumn(final String label, final String path, String el, Class<?> type, String subType) {
		return addComboBoxColumn(label, path, el, type, subType, null);
	}

	/**
	 * Adds a column binding with a {@link JComboBox} using a {@link SimpleELListRenderer} with the given el expression.<br>
	 * The combobox content list will be loaded using {@link ListManager#getList(Class, String, Map)}.
	 * 
	 * @param label The column label
	 * @param path The path to the property shown in this column
	 * @param el The simple el expression to use by the combobox renderer
	 * @param type The content type of the combobox list
	 * @param subType Additional string specifying the "subtype" of the combobox content list (may be null)
	 * @param settings Additional settings for the combobox content load (may be null)
	 * @return The {@link BindingInfos} object for further configuration of the binding
	 * @throws IllegalStateException if the table is already bound
	 */
	public ColumnBindingInfos addComboBoxColumn(final String label, final String path, String el, Class<?> type, String subType, Map<Object, Object> settings) {
		final ColumnBindingInfos binding = mapping.addComboBoxColumn(label, path, el, type, subType, settings);
		((SimpleELTableCellRenderer) binding.getRenderer()).setShowStatus(showStatus);
		return binding;
	}

	/**
	 * Adds a the given action to the popup.
	 * 
	 * @param action The action
	 */
	public void addLocalAction(ISelectionAction<T> action) {
		popupManager.addLocalAction(action);
	}

	/**
	 * Adds a the given action to the popup at the given location.
	 * 
	 * @param path The location where to add the action
	 * @param action The action
	 */
	public void addLocalAction(String path, ISelectionAction<T> action) {
		popupManager.addLocalAction(path, action);
	}

	/**
	 * @return Returns the popupManager.
	 */
	public LocalPopupManager getPopupManager() {
		return popupManager;
	}

	/**
	 * Adds a selection binding to the given {@link MappingGroup}.<br>
	 * If a binding with the same name exists already, then it's removed before the new one is added.
	 * 
	 * @param name The name of the selection binding
	 * @param group The binding group to add
	 * @see ch.trackedbean.binding.mapping.TableMapping#addSelectionBinding(java.lang.String, ch.trackedbean.binding.mapping.MappingGroup)
	 */
	public void addSelectionBinding(final String name, final MappingGroup<T> group) {
		mapping.addSelectionBinding(name, group);
	}

	/**
	 * Adds a selection binding to the given {@link MappingGroup}.<br>
	 * If a binding with the same name exists already, then it's removed before the new one is added.
	 * 
	 * @param name The name of the selection binding
	 * @param relativePath The path starting at the returned row
	 * @param group The binding group to add
	 * @see ch.trackedbean.binding.mapping.TableMapping#addSelectionBinding(java.lang.String, java.lang.String, ch.trackedbean.binding.mapping.MappingGroup)
	 */
	public void addSelectionBinding(final String name, final String relativePath, final MappingGroup<T> group) {
		mapping.addSelectionBinding(name, relativePath, group);
	}

	/**
	 * Adds a column binding to a statusproperty
	 * 
	 * @param label The column label
	 * @param path The path to the property
	 * @throws IllegalStateException if the table is already bound
	 * @see ch.trackedbean.binding.mapping.TableMapping#addStatusColumn(java.lang.String, java.lang.String)
	 */
	public void addStatusColumn(final String label, final String path) {
		mapping.addStatusColumn(label, path);
	}

	/**
	 * Returns the status of the value in the given cell
	 * 
	 * @param row The row
	 * @param col The column
	 * @return The status of the value in the given cell or null if the cell doesen't exists
	 * @see ch.trackedbean.binding.mapping.TableMapping#getCellStatus(int, int)
	 */
	public Status getCellStatus(final int row, final int col) {
		return mapping.getCellStatus(row, col);
	}

	/**
	 * @return If the table is editable
	 * @see ch.trackedbean.binding.mapping.TableMapping#isReadOnly()
	 */
	public boolean isReadOnly() {
		return mapping.isReadOnly();
	}

	/**
	 * Removes the tablemodel
	 * 
	 * @see ch.trackedbean.binding.mapping.TableMapping#removeModel()
	 */
	public void removeModel() {
		mapping.removeModel();
	}

	/**
	 * Removes the selection binding with the given name
	 * 
	 * @param name The name of the selection binding
	 * @see ch.trackedbean.binding.mapping.TableMapping#removeSelectionBinding(java.lang.String)
	 */
	public void removeSelectionBinding(final String name) {
		mapping.removeSelectionBinding(name);
	}

	/**
	 * @param readOnly True if the table has to be readonly
	 * @see ch.trackedbean.binding.mapping.TableMapping#setReadOnly(boolean)
	 */
	public void setEditable(final boolean readOnly) {
		mapping.setReadOnly(readOnly);
	}

	/**
	 * Sets the table model
	 * 
	 * @param list The list used as tablemodel
	 * @see ch.trackedbean.binding.mapping.TableMapping#setModel(java.util.List)
	 */
	public void setModel(final List<T> list) {
		mapping.setModel(list);
	}

	/**
	 * @return The current tablemodel (may be null)
	 * @see ch.trackedbean.binding.mapping.TableMapping#getModel()
	 */
	public List<T> getModel() {
		return mapping.getModel();
	}

	/**
	 * Returns the object representing the selected row.
	 * 
	 * @return The selected object or null
	 */
	public T getSelectedRow() {
		return mapping.getSelectedRow();
	}

	/**
	 * Returns a list of all selected rows.
	 * 
	 * @return The selected rows
	 */
	public List<T> getSelectedRows() {
		return mapping.getSelectedRows();
	}

	/**
	 * Returns the default action of the table (so the action which will be executed when a double click occours)
	 * 
	 * @return the defaultAction or null
	 */
	public ISelectionAction getDefaultAction() {
		return defaultAction;
	}

	/**
	 * Sets the default action of the table (so the action which will be executed when a double click occours)
	 * 
	 * @param defaultAction the defaultAction to set or null
	 */
	public void setDefaultAction(ISelectionAction<T> defaultAction) {
		this.defaultAction = defaultAction;
	}
}
